package codechicken.wirelessredstone.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Properties;
import java.util.TreeMap;

import net.minecraft.src.Block;
import net.minecraft.src.ModLoader;
import net.minecraft.src.World;

public class SaveManager
{
	public static SaveManager getInstance(int dimension)
	{
		return managers[dimension];
	}

	public static void reloadSave(World world, int dim)
	{
		managers[dim] = new SaveManager(world, dim);
	}
	
	public static void reloadSave(World world)
	{
		managers[CoreProxy.getArrayDimension(world)] = new SaveManager(world, CoreProxy.getArrayDimension(world));
	}
	
	private SaveManager(World world, int dimension)
	{
		try
		{
			this.dimension = dimension;
			File etherdir = getEtherDir(world, dimension);
			File file = new File(etherdir, "fmap.dat");
			boolean newlycreated = false;
			if(!file.exists())
			{
				file.createNewFile();
				newlycreated = true;
			}			
			freqmapfile = new RandomAccessFile(file, "rw");
			if(newlycreated)
			{
				for(int i = 0; i < 5000; i++)
				{
					freqmapfile.writeShort(-1);
				}
			}
			
			file = new File(etherdir, "node1.dat");
			if(!file.exists())
			{
				file.createNewFile();
			}
			smallsectorfile = new RandomAccessFile(file, "rw");
			
			file = new File(etherdir, "node2.dat");
			if(!file.exists())
			{
				file.createNewFile();
			}
			largesectorfile = new RandomAccessFile(file, "rw");
			
			if(dimension == 0)
			{
				file = new File(etherdir, "fprop.dat");
				if(!file.exists())
				{
					file.createNewFile();
				}
				freqpropfile = file;
				freqproperties = new Properties();
				
				file = new File(etherdir, "gprop.dat");
				if(!file.exists())
				{
					file.createNewFile();
				}
				generalpropfile = file;
				generalproperties = new Properties();
				generalproperties.load(new FileInputStream(generalpropfile));
			}
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
		}
	}

	private File getEtherDir(World world, int dimension)
	{		
		File dir = new File(CoreProxy.getWorldDirectory(world, CoreProxy.getActualDimension(dimension)).toString() + File.separator + "RedstoneEther");
		if(!dir.exists())
		{
			dir.mkdirs();
		}
		return dir;
	}
	
	private void setFreqSector(int freq, int sector)
	{
		try
		{
			freqoffsets[(freq-1)] = sector;
			freqmapfile.seek((freq-1)*2);
			freqmapfile.writeShort(sector);
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
		}
	}
	
	private void seekSector(int sector)
	{		
		try
		{
			if(sector >= largesectoroffset)
			{
				rwfile = largesectorfile;
				sector -= largesectoroffset;
				rwfile.seek(sector * largesectorsize);
			}
			else
			{
				rwfile = smallsectorfile;
				rwfile.seek(sector * smallsectorsize);
			}
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
		}
	}
	
	private void setSectorUsed(int sector, boolean used)
	{
		ArrayList<Boolean> usedsectors = (sector >= largesectoroffset) ? usedlargesectors : usedsmallsectors;
		if(sector >= largesectoroffset)
		{
			sector -= largesectoroffset;
		}
		usedsectors.set(sector, used);
	}
	
	private int getNewSector(boolean large)
	{
		try
		{
			if(large)
			{
				RandomAccessFile file = largesectorfile;
				
				long prevpos = file.getFilePointer();
				
				file.seek(file.length());
				file.writeShort(-1);//the next sector is not set
				file.write(new byte[largesectorsize - 2]);//fill the rest of this sector with 0's
				file.seek(prevpos);
				
				usedlargesectors.add(false);
				
				return ((int) file.length() / largesectorsize) - 1 + largesectoroffset;
			}
			else
			{
				RandomAccessFile file = smallsectorfile;
				
				long prevpos = file.getFilePointer();
				
				file.seek(file.length());
				file.writeShort(-1);//the next sector is not set
				file.write(new byte[smallsectorsize - 2]);//fill the rest of this sector with 0's
				file.seek(prevpos);
				
				usedsmallsectors.add(false);
				
				return ((int) file.length() / smallsectorsize) - 1;
			}
			
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
			return -1;
		}
	}
	
	private int getUnusedSector(int numnodes)
	{
		boolean largesector = (numnodes > maxsmallnodes);
		ArrayList<Boolean> usedsectors = largesector ? usedlargesectors : usedsmallsectors;
		
		for(int i = 0; i < usedsectors.size(); i++)
		{
			if(!usedsectors.get(i))
			{
				if(largesector)
				{
					return i + largesectoroffset;
				}
				else
				{
					return i;
				}
			}
		}
		
		return getNewSector(numnodes > maxsmallnodes);
	}
	
	private void setNextSector(int sector, int nextsector)
	{
		seekSector(sector);
		try
		{
			rwfile.writeShort((short)nextsector);
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
		}
	}
	
	private void setSectorLength(int sector, int numnodes)
	{
		seekSector(sector);
		try
		{
			rwfile.skipBytes(2);
			rwfile.writeShort((short)numnodes);
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
		}
	}
	
	private int getNextSector(int sector)
	{
		seekSector(sector);
		try
		{
			int nextsector = rwfile.readShort() & 0xFFFF;
			if(nextsector == 0xFFFF)//empty handling
			{
				nextsector = -1;
			}
			return nextsector;
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
			return -1;
		}
	}
	
	private int getSectorLength(int sector)
	{
		seekSector(sector);
		try
		{
			rwfile.skipBytes(2);
			return rwfile.readShort() & 0xFFFF;
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
			return -1;
		}		
	}
	
	private void markSectorStackUnused(int firstsector)
	{
		int nextsector = firstsector;
		while(true)//sector unused setting loop
		{
			if(nextsector == -1)
			{
				break;
			}
			int sector = nextsector;
			setSectorUsed(sector, false);
			setNextSector(sector, -1);
			nextsector = getNextSector(nextsector);
		}
	}
	
	private void writeNodes(int freq, int numnodes, ArrayList<BlockCoord> nodes)
	{
		try
		{		
			int nextsector = freqoffsets[freq-1];
			if(nextsector == -1)
			{
				nextsector = getUnusedSector(numnodes);
				setSectorUsed(nextsector, true);
				setFreqSector(freq, nextsector);
			}
			else
			{
				markSectorStackUnused(nextsector);
				setSectorUsed(nextsector, true);
			}
			int thissector;
			
			int writtennodes;
			boolean largesector;
			int nodespersector;
			
			while(true)//sector following / creation loop
			{
				seekSector(nextsector);
				rwfile.skipBytes(4);//skip the header info
				
				thissector = nextsector;
				writtennodes = 0;
				largesector = nextsector >= largesectoroffset;
				nodespersector = largesector ? largesectornodes : smallsectornodes;
				
				while(writtennodes != nodespersector && numnodes > 0)//sector node writing loop
				{
					BlockCoord node = nodes.get(nodes.size() - numnodes);
					
					rwfile.writeInt(node.x);
					rwfile.writeInt(node.y);
					rwfile.writeInt(node.z);
				
					numnodes--;
					writtennodes++;
				}
				
				setSectorLength(thissector, writtennodes);
				
				if(numnodes == 0)//finished
				{
					setNextSector(thissector, -1);
					break;
				}

				nextsector = getUnusedSector(numnodes);
				setSectorUsed(nextsector, true);
				setNextSector(thissector, nextsector);
			}
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
		}
	}
	
	public void saveFreq(int freq, int activetransmitters, TreeMap<BlockCoord, Boolean> transmittermap)
	{		
		try
		{			
			int numnodes = 0;
			ArrayList<BlockCoord> nodes = new ArrayList<BlockCoord>(activetransmitters);
			
			for(Iterator<BlockCoord> iterator = transmittermap.keySet().iterator(); iterator.hasNext() && numnodes < activetransmitters;)
			{
				BlockCoord node = iterator.next();
				if(transmittermap.get(node))
				{
					nodes.add(node);
					numnodes++;
				}
			}
			
			if(numnodes == 0)
			{
				setFreqSector(freq, -1);
				return;
			}
			
			writeNodes(freq, numnodes, nodes);
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
		}
	}
	
	private void loadFreqMap()
	{
		try
		{
			freqoffsets = new int[RedstoneEther.numfreqs];
			freqmapfile.seek(0);
			
			for(int freq = 1; freq <= RedstoneEther.numfreqs; freq++)//read freq offsets
			{		
				freqoffsets[freq-1] = freqmapfile.readShort() & 0xFFFF;
				if(freqoffsets[freq-1] == 0xFFFF)//empty handling
				{
					freqoffsets[freq-1] = -1;
				}
			}
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
		}
	}
	
	private void initSectors()
	{
		int numsmallsectors;
		int numlargesectors;
		try
		{
			numsmallsectors = (int) (smallsectorfile.length() / smallsectorsize);
			numlargesectors = (int) (largesectorfile.length() / largesectorsize);
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
			return;
		}
		
		usedsmallsectors = new ArrayList<Boolean>(numsmallsectors);
		for(int i = 0; i < numsmallsectors; i++)//intialize array
		{
			usedsmallsectors.add(false);
		}
		
		usedlargesectors = new ArrayList<Boolean>(numlargesectors);
		for(int i = 0; i < numlargesectors; i++)//intialize array
		{
			usedlargesectors.add(false);
		}
	}
	
	private void loadNodes()
	{		
		for(int freq = 1; freq <= RedstoneEther.numfreqs; freq++)
		{
			int nextsector = freqoffsets[freq-1];
			
			if(nextsector == -1)
			{
				continue;
			}
			
			while(true)
			{
				seekSector(nextsector);
				setSectorUsed(nextsector, true);
				
				int numnodes = getSectorLength(nextsector);
				
				for(int j = 0; j < numnodes; j++)
				{
					try
					{
						RedstoneEther.loadTransmitter(dimension, rwfile.readInt(), rwfile.readInt(), rwfile.readInt(), freq);
					}
					catch(Exception e)
					{
						ModLoader.ThrowException("Wireless Redstone Save Manager", e);
					}
				}

				nextsector = getNextSector(nextsector);
				if(nextsector == -1)//end of list
				{
					break;
				}
			}
			
			RedstoneEther.setFreqClean(freq, dimension);
		}
	}
	
	public void removeTrailingSectors()
	{
		try
		{
			if(lastcleanuptime != 0 && System.currentTimeMillis() - lastcleanuptime < cleanuptimeneeded)//0 means loading
			{
				return;
			}
			int lastusedsector = -1;
			
			for(int i = 0; i < usedlargesectors.size(); i++)//find the last used sector
			{
				if(usedlargesectors.get(i))
				{
					lastusedsector = i;
				}
			}
			for(int i = usedlargesectors.size() - 1; i > lastusedsector; i--)//remove sectors from list
			{
				usedlargesectors.remove(i);
			}
			largesectorfile.setLength((lastusedsector+1)*largesectorsize);//truncate the file
			
			for(int i = 0; i < usedsmallsectors.size(); i++)
			{
				if(usedsmallsectors.get(i))
				{
					lastusedsector = i;
				}
			}
			
			for(int i = usedsmallsectors.size() - 1; i > lastusedsector; i--)
			{
				usedsmallsectors.remove(i);
			}
			smallsectorfile.setLength((lastusedsector+1)*smallsectorsize);
			
			lastcleanuptime = System.currentTimeMillis();
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
		}
	}
	
	public void loadEther()
	{
		loadFreqMap();
		initSectors();
		loadNodes();
		lastcleanuptime = 0;
		removeTrailingSectors();
	}
	
	public void saveFreqInfo(int freq, int colourid, String name)
	{
		if(loadinginfo)
		{
			return;
		}
		
		String colourkey = Integer.toString(freq)+".c";
		String namekey = Integer.toString(freq)+".n";
		
		if(colourid == -1)
		{
			if(freqproperties.containsKey(colourkey))
			{
				freqproperties.remove(colourkey);
			}
		}
		else
		{
			freqproperties.setProperty(colourkey, Integer.toString(colourid));
		}
		
		if(name == "" || name == null)
		{
			if(freqproperties.containsKey(namekey))
			{
				freqproperties.remove(namekey);
			}
		}
		else
		{
			freqproperties.setProperty(namekey, name);
		}
		
		try
		{
			freqproperties.store(new FileOutputStream(freqpropfile), null);
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
		}
	}
	
	public void loadFreqInfo()
	{
		loadinginfo = true;
		
		try
		{
			freqproperties.load(new FileInputStream(freqpropfile));
			for(@SuppressWarnings("rawtypes")Enumeration e = freqproperties.propertyNames(); e.hasMoreElements();)
			{
				String key = (String)e.nextElement();
				int freq = Integer.parseInt(key.substring(0, key.indexOf('.')));
				
				if(key.endsWith(".c"))
				{
					RedstoneEther.setFreqColour(freq, Integer.parseInt(freqproperties.getProperty(key)));
				}
				else if(key.endsWith(".n"))
				{
					RedstoneEther.setFreqName(freq, freqproperties.getProperty(key));
				}
			}
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
		}
		loadinginfo = false;
	}
	
	public void setProperty(String property, int value)
	{
		setProperty(property, Integer.toString(value));
	}
	
	public void setProperty(String property, boolean value)
	{
		setProperty(property, Boolean.toString(value));
	}
	
	public void setProperty(String property, String value)
	{
		generalproperties.setProperty(property, value);
		saveGeneralProperties();
	}
	
	private void saveGeneralProperties()
	{
		try
		{
			generalproperties.store(new FileOutputStream(generalpropfile), null);
		}
		catch(Exception e)
		{
			ModLoader.ThrowException("Wireless Redstone Save Manager", e);
		}
	}
	
	public int getProperty(String property, int defaultvalue)
	{
		return Integer.parseInt(getProperty(property, Integer.toString(defaultvalue)));
	}
	
	public String getProperty(String property, String defaultvalue)
	{
		String value = generalproperties.getProperty(property);
		if(value == null)
		{
			setProperty(property, defaultvalue);
			return defaultvalue;
		}
		else
		{
			return value;
		}
	}
	
	public boolean getProperty(String property, boolean defaultvalue)
	{
		return Boolean.parseBoolean(getProperty(property, Boolean.toString(defaultvalue)));
	}
	
	public static ConfigFile config()
	{
		return globalconfig;
	}
	
	public static int getFreeBlockID(int preferred)
	{
		for(int i = preferred; i < 255; i++)
		{
			if(Block.blocksList[i] == null)
			{
				return i;
			}
		}
		for(int i = preferred - 1; i > 0; i--)
		{
			if(Block.blocksList[i] == null)
			{
				return i;
			}
		}
		return -1;
	}
	
	private RandomAccessFile freqmapfile;
	private RandomAccessFile smallsectorfile;
	private RandomAccessFile largesectorfile;
	private RandomAccessFile rwfile;
	
	private File freqpropfile;
	private File generalpropfile;
	private Properties freqproperties;
	private Properties generalproperties;
	private static ConfigFile globalconfig;

	private ArrayList<Boolean> usedsmallsectors;
	private ArrayList<Boolean> usedlargesectors;
	private int[] freqoffsets;
	
	private long lastcleanuptime;
	
	private int dimension;
	
	private boolean loadinginfo;
	
	private static SaveManager[] managers = new SaveManager[2];
	
	private final int largesectorsize = 256;
	private final int largesectornodes = (int)(largesectorsize / 12);
	private final int smallsectorsize = 64;
	private final int smallsectornodes = (int)(smallsectorsize / 12);
	
	private final int largesectoroffset = 32767;
	private final int maxsmallnodes = 5;
	
	private final int cleanuptimeneeded = 300*1000;//5 min
	
	static
	{
		globalconfig = new ConfigFile(new File(CoreProxy.getMinecraftDir()+"/config", "WirelessRedstone.cfg")).setComment("Wireless Redstone Chicken Bones Edition Configuration File:Deleting any element will restore it to it's default value:Block ID's will be automatically generated the first time it's run");
	}
}
